/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
 */

#ifndef PANDA_PLUGINS_ECMASCRIPT_RUNTIME_INTERPRETER_JS_DECODE_CALL_INSTR_H
#define PANDA_PLUGINS_ECMASCRIPT_RUNTIME_INTERPRETER_JS_DECODE_CALL_INSTR_H

#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/js_thread.h"

namespace ark::ecmascript {
template <BytecodeInstruction::Format FORMAT, BytecodeInstruction::Opcode OPCODE, bool IS_QUICKENED>
ALWAYS_INLINE inline uint32_t JSGetNumberActualArgsDyn([[maybe_unused]] BytecodeInstruction inst)
{
    using R = BytecodeInstructionResolver<IS_QUICKENED>;
    constexpr auto OP = R::template Get<OPCODE>();
    if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_NONE_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 0;
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL1DYN_PREF_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 1;
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL2DYN_PREF_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 2;
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL3DYN_PREF_V8_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 3;
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_IMM16_V8_PROF16>());
        auto imm = inst.GetImm<FORMAT>();
        return js_method_args::FIRST_ARG_IDX + imm;
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL0THISDYN_PREF_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX;
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL1THISDYN_PREF_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 1;
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL2THISDYN_PREF_V8_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 2;
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_CALL3THISDYN_PREF_V8_V8_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_V8_V8_PROF16>());
        return js_method_args::FIRST_ARG_IDX + 3;
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_CALLITHISRANGEDYN_PREF_IMM16_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_IMM16_V8_PROF16>());
        auto imm = inst.GetImm<FORMAT>();
        ASSERT(imm >= 1);  // magic, 'this' is counted in range, 'func' - not
        return js_method_args::FIRST_ARG_IDX + (imm - 1);
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_NEWOBJDYNRANGE_PREF_IMM16_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_IMM16_V8_PROF16>());
        auto imm = inst.GetImm<FORMAT>();
        ASSERT(imm >= 2);  // magic, 'func' and 'new_target' are counted in range
        return js_method_args::FIRST_ARG_IDX + (imm - 2);
    } else {
        enum { IMPOSSIBLE_CASE = false };
        static_assert(IMPOSSIBLE_CASE, "Impossible case");
    }

    UNREACHABLE_CONSTEXPR();
    return 0;
}

template <BytecodeInstruction::Format FORMAT, BytecodeInstruction::Opcode OPCODE, bool IS_QUICKENED>
ALWAYS_INLINE inline int64_t JSGetCalleDyn([[maybe_unused]] Frame *frame, [[maybe_unused]] BytecodeInstruction inst)
{
    using R = BytecodeInstructionResolver<IS_QUICKENED>;
    constexpr auto OP = R::template Get<OPCODE>();

    if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16>()) {
        return frame->GetAccAsVReg().GetValue();
    } else {
        return frame->GetVReg(inst.GetVReg<FORMAT, 0>()).GetValue();
    }

    UNREACHABLE_CONSTEXPR();
    return 0;
}

template <BytecodeInstruction::Format FORMAT, BytecodeInstruction::Opcode OPCODE, bool IS_QUICKENED>
ALWAYS_INLINE inline uint16_t JSGetCalleRangeStartDyn([[maybe_unused]] BytecodeInstruction inst)
{
    using R = BytecodeInstructionResolver<IS_QUICKENED>;
    constexpr auto OP = R::template Get<OPCODE>();

    if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8_PROF16>() ||
                  OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALLITHISRANGEDYN_PREF_IMM16_V8_PROF16>() ||
                  OP == R::template Get<BytecodeInstruction::Opcode::ECMA_NEWOBJDYNRANGE_PREF_IMM16_V8_PROF16>()) {
        return inst.GetVReg<FORMAT, 0>();
    }

    UNREACHABLE_CONSTEXPR();
    return 0;
}

template <BytecodeInstruction::Format FORMAT, BytecodeInstruction::Opcode OPCODE, bool IS_QUICKENED>
ALWAYS_INLINE inline static void JSCopyArgumets(JSThread *thread, Frame *prevFrame, uint64_t rawFnObject,
                                                BytecodeInstruction prevInst, Frame *newFrame, uint32_t numVregs,
                                                uint32_t numActualArgs)
{
    // ecma.call0dyn
    // ecma.call1dyn
    // ecma.call2dyn
    // ecma.call3dyn
    // ecma.callirangedyn:
    //   dyn_arg[0] - vreg[  0] [functional object]
    //   dyn_arg[1] - undefined [    new.target   ]
    //   dyn_arg[2] - undefined | global object
    //   dyn_arg[3] - vreg[  1] [    args[  0]    ]
    //   dyn_arg[4] - vreg[  2] [    args[  1]    ]
    //   dyn_arg[5] - vreg[  3] [    args[  2]    ]
    //     ...
    //   dyn_arg[N] - vreg[N-2] [    args[N-3]    ]

    // ecma.call0thisdyn
    // ecma.call1thisdyn
    // ecma.call2thisdyn
    // ecma.call3thisdyn
    // ecma.callithisrangedyn:
    //   dyn_arg[0] - vreg[  0] [functional object]
    //   dyn_arg[1] - undefined [    new.target   ]
    //   dyn_arg[2] - vreg[  1] [      this       ]
    //   dyn_arg[3] - vreg[  2] [    args[  0]    ]
    //   dyn_arg[4] - vreg[  3] [    args[  1]    ]
    //     ...
    //   dyn_arg[N] - vreg[N-1] [    args[N-3]    ]

    // Where vreg[N-1] is accumulator

    // ecma.newobjdynrange
    //   dyn_arg[0] - vreg[  0] [functional object]
    //   dyn_arg[1] - vreg[  1] [    new.target   ]
    //   dyn_arg[2] - allocated this, untouched
    //   dyn_arg[3] - vreg[  2] [    args[  0]    ]
    //   dyn_arg[4] - vreg[  3] [    args[  1]    ]
    //     ...
    //   dyn_arg[N] - vreg[N-1] [    args[N-3]    ]

    using R = BytecodeInstructionResolver<IS_QUICKENED>;
    constexpr auto OP = R::template Get<OPCODE>();

    ASSERT(numActualArgs >= js_method_args::FIRST_ARG_IDX);
    newFrame->GetVReg(numVregs + js_method_args::FUNC_IDX).SetValue(rawFnObject);
    if constexpr (OP != R::template Get<BytecodeInstruction::Opcode::ECMA_NEWOBJDYNRANGE_PREF_IMM16_V8_PROF16>()) {
        newFrame->GetVReg(numVregs + js_method_args::NEW_TARGET_IDX).SetValue(JSTaggedValue::VALUE_UNDEFINED);
    }

    if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16>() ||
                  OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL1DYN_PREF_V8_PROF16>() ||
                  OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL2DYN_PREF_V8_V8_PROF16>() ||
                  OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL3DYN_PREF_V8_V8_V8_PROF16>() ||
                  OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8_PROF16>()) {
        JSTaggedValue fnObject(rawFnObject);
        uint64_t thisArg = JSTaggedValue::VALUE_UNDEFINED;
        if (fnObject.IsJSFunction() && !JSFunction::Cast(fnObject.GetHeapObject())->IsStrict()) {
            thisArg = thread->GetGlobalObject().GetRawData();
        }
        newFrame->GetVReg(numVregs + js_method_args::THIS_IDX).SetValue(thisArg);
    }

    if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_NONE_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX);
        // Do nothing
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL1DYN_PREF_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX + 1);
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 0).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL2DYN_PREF_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX + 2);
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 0) = prevFrame->GetVReg(prevInst.GetVReg(1));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 1).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL3DYN_PREF_V8_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX + 3);
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 0) = prevFrame->GetVReg(prevInst.GetVReg(1));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 1) = prevFrame->GetVReg(prevInst.GetVReg(2));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 2).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_IMM16_V8_PROF16>());
        uint16_t prevV0 = JSGetCalleRangeStartDyn<FORMAT, OPCODE, IS_QUICKENED>(prevInst);
        for (size_t i = 0; i < (numActualArgs - js_method_args::FIRST_ARG_IDX); ++i) {
            newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + i) = prevFrame->GetVReg(prevV0 + 1 + i);
        }
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL0THISDYN_PREF_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX);
        newFrame->GetVReg(numVregs + js_method_args::THIS_IDX).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL1THISDYN_PREF_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX + 1);
        newFrame->GetVReg(numVregs + js_method_args::THIS_IDX) = prevFrame->GetVReg(prevInst.GetVReg(1));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 0).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP == R::template Get<BytecodeInstruction::Opcode::ECMA_CALL2THISDYN_PREF_V8_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX + 2);
        newFrame->GetVReg(numVregs + js_method_args::THIS_IDX) = prevFrame->GetVReg(prevInst.GetVReg(1));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 0) = prevFrame->GetVReg(prevInst.GetVReg(2));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 1).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_CALL3THISDYN_PREF_V8_V8_V8_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_V8_V8_V8_V8_PROF16>());
        ASSERT(numActualArgs == js_method_args::FIRST_ARG_IDX + 3);
        newFrame->GetVReg(numVregs + js_method_args::THIS_IDX) = prevFrame->GetVReg(prevInst.GetVReg(1));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 0) = prevFrame->GetVReg(prevInst.GetVReg(2));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 1) = prevFrame->GetVReg(prevInst.GetVReg(3));
        newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + 2).SetValue(prevFrame->GetAccAsVReg().GetValue());
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_CALLITHISRANGEDYN_PREF_IMM16_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_IMM16_V8_PROF16>());
        uint16_t prevV0 = JSGetCalleRangeStartDyn<FORMAT, OPCODE, IS_QUICKENED>(prevInst);
        newFrame->GetVReg(numVregs + js_method_args::THIS_IDX) = prevFrame->GetVReg(prevV0 + 1);
        for (size_t i = 0; i < (numActualArgs - js_method_args::FIRST_ARG_IDX); ++i) {
            newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + i) = prevFrame->GetVReg(prevV0 + 2 + i);
        }
    } else if constexpr (OP ==
                         R::template Get<BytecodeInstruction::Opcode::ECMA_NEWOBJDYNRANGE_PREF_IMM16_V8_PROF16>()) {
        static_assert(FORMAT == R::template Get<BytecodeInstruction::Format::PREF_IMM16_V8_PROF16>());
        uint16_t prevV0 = JSGetCalleRangeStartDyn<FORMAT, OPCODE, IS_QUICKENED>(prevInst);
        newFrame->GetVReg(numVregs + js_method_args::NEW_TARGET_IDX) = prevFrame->GetVReg(prevV0 + 1);
        for (size_t i = 0; i < (numActualArgs - js_method_args::FIRST_ARG_IDX); ++i) {
            newFrame->GetVReg(numVregs + js_method_args::FIRST_ARG_IDX + i) = prevFrame->GetVReg(prevV0 + 2 + i);
        }
    } else {
        enum { IMPOSSIBLE_CASE = false };
        static_assert(IMPOSSIBLE_CASE, "Impossible case");
    }

    for (size_t i = numVregs + numActualArgs; i < newFrame->GetSize(); ++i) {
        newFrame->GetVReg(i).SetValue(JSTaggedValue::VALUE_UNDEFINED);
    }
}
}  // namespace ark::ecmascript

#endif  // PANDA_PLUGINS_ECMASCRIPT_RUNTIME_INTERPRETER_JS_DECODE_CALL_INSTR_H
